import java.net.*; 
import java.util.HashMap;

class UDPClient { 
	/*the interval length for members' checking aliveness*/
	static protected int TIME = 10000;
	/*local address and porNnum are set in constructor*/ 
	protected String userName = "anonymous";
	protected InetAddress localAddr;
	protected int localPortNum;
	protected DatagramSocket dtSocket;
	/*Server address and portNum are set via setter*/
	private InetAddress serverAddr;
	private int serverPortNum;
	/*A list of existing users, key: unique userID, value:node recording a user info, include group leader*/
	protected HashMap<String, UDPNode> memberList;
	private int ServerLife;	

	UDPClient(int portNum, String username){
		try{//Set local IpAddress, portNum, userName
			localAddr = InetAddress.getLocalHost();
			this.localPortNum = portNum;
			this.userName = username;			
		}catch(UnknownHostException e){
			System.out.println("Unable to get local host.");
		}
		try {
			this.dtSocket = new DatagramSocket(this.localPortNum, this.localAddr);
		} catch (SocketException e) {
			System.out.println("Error in Initializing DatagramSocket!");
			e.printStackTrace();
		}
		this.memberList = new HashMap<String, UDPNode>();
		this.ServerLife = 3;
		System.out.println("---------------Welcome "+ this.getUserName()+"---------------");
		System.out.println("Your Local IpAddress:"+this.getLocalIpAddress());
		System.out.println("Your Local PortNumber:"+this.getLocalPortNum());

	}
	/**
	 * Constructor only called by UDPServer's constructor when changing UDPClient to a UDPServer
	 * @param portNum
	 * @param username
	 * @param socket
	 * @param memberList
	 */
	protected UDPClient(int portNum, String username, DatagramSocket socket, HashMap<String, UDPNode> memberList){
		try {
			localAddr = InetAddress.getLocalHost();
			this.localPortNum = portNum;
			this.userName = username;
		} catch (UnknownHostException e) {
			System.out.println("Unable to get local host.");
			e.printStackTrace();
		}
		this.dtSocket = socket;
		this.memberList = memberList;
	}

	/**
	 * Report alive to Server when current object is UDPClient not UDPServer
	 */
	public void reportAlive(){
		if(!UDPServer.class.equals(this.getClass())){			
			//aliveMsg contains basic info for each user
			String aliveMsg = new AliveMsg(this).toString();			
			DatagramPacket alivePacket = new DatagramPacket(aliveMsg.getBytes(), aliveMsg.getBytes().length, this.getServerAddr(), this.getServerPortNum());			
			try {
				this.getUDPSocket().send(alivePacket);
			} catch (Exception e) {
				System.out.println("Oops, AliveMsg send failed!");
				e.printStackTrace();
			}
		}
	}

	
	/**
	 * Background method running to check whether server is still alive.
	 * Decrease server's life by 1 in each round
	 */
	public void checkServerAlive(){
		
		while(true){
			this.ServerLife -=1;
			if(this.ServerLife<0){
				System.out.println("Oh my god, leader is gone!!");
				this.memberList.remove("IP:"+this.getServerAddr().getHostAddress()+":PORT:"+this.getServerPortNum());				
				if(this.tryBecomeLeader()){
					break;
				}else{
					this.ServerLife = 3;
				}
			}
			try {
				Thread.sleep(this.TIME);
			} catch (InterruptedException e) {
				System.out.println("Oops, something bad happened when thread sleep");
				e.printStackTrace();
			}
		}
	}
	/**
	 * Try to become this group's leader to replace the previous leader left 
	 * If IP address and portNum is the smallest, then become leader
	 * else cannot become the leader 
	 */
	private boolean tryBecomeLeader(){
		String myIpPlusPort = this.getLocalIpAddress()+this.getLocalPortNum();
		String smallestIpPlusPort = myIpPlusPort;
		for(UDPNode member : this.memberList.values()){
			String memberIpPLusPort = member.getIpAddress().getHostAddress()+member.getPortNum(); 
			if(memberIpPLusPort.compareTo(smallestIpPlusPort)<0){
				smallestIpPlusPort = memberIpPLusPort;
				break;
			}
		}
		if(myIpPlusPort.equals(smallestIpPlusPort)){//becomes the leader
			System.out.println("Hey, isn't it wonderful, i will be our new leader!!!yeah~~~");
			/*1.new a UDPServer, remove all this UDPClient's info to UDPServer instance*/
			UDPServer newServer = new UDPServer(this);
			//2.broadcast to everyone: old leader left, i am the new leader
			System.out.println("Congrats! you are the leader now!");
			newServer.broadcastMessage(newServer.userName+": Hey everyone, i am the new leader!");
			String myKey = "IP:"+this.getLocalIpAddress()+":PORT:"+this.getLocalPortNum();
			this.memberList.put(myKey, new UDPNode(userName, this.getLocalInetAddress(), this.localPortNum, true));
			newServer.broadcastMessage(newServer.memberListToString());
			ReadThread readThread = new ReadThread(newServer);
			WriteThread writeThread = new WriteThread(newServer);
			new Thread(readThread).start();
			new Thread(writeThread).start();
			newServer.aliveCheck();
			this.finalize();
			return true;
		}
		return false;
	}

	public void updateServerLife(){
		this.ServerLife = 3;
	}
	public void setServerAddr(String sIpAddress){
		try {
			this.serverAddr = InetAddress.getByName(sIpAddress);
		} catch (UnknownHostException e) {
			System.out.println("Cannot resolve this server hostname!");
			e.printStackTrace();
		}
	}
	public void setServerPortNum(int num){
		this.serverPortNum = num;
	}
	public void setMemberList(HashMap<String, UDPNode> memberList){
		this.memberList = memberList;
	}
	public void putInMemberList(String key, UDPNode node){
		this.memberList.put(key, node);
		if(node.isLeader()){
			this.setServerAddr(node.getIpAddress().getHostName());
			this.setServerPortNum(node.getPortNum());
		}
	}

	//Getters
	public HashMap<String, UDPNode> getMemberList(){
		return this.memberList;		
	}
	public String getUserName(){
		return this.userName;
	}
	public String getLocalHostname(){
		return this.localAddr.getHostName();
	}
	public InetAddress getLocalInetAddress(){
		return this.localAddr;
	}
	public String getLocalIpAddress(){
		return this.localAddr.getHostAddress();
	}
	public int getLocalPortNum(){
		return this.localPortNum;
	}
	public DatagramSocket getUDPSocket(){
		return this.dtSocket;
	}
	public int getServerLife(){
		return this.ServerLife;
	}
	//Deprecated in subclass UDPServer 
	public InetAddress getServerAddr(){
		return this.serverAddr;
	}
	//Deprecated in subclass UDPServer
	public int getServerPortNum(){
		return this.serverPortNum;
	}
	//Deprecated in subclass UDPServer
	public void closeUDPSocket(){
		this.dtSocket.close();
	}
	@Override
	protected void finalize(){}


} 
